package com.wwg.widget.impl;

import android.animation.Animator;
import android.animation.AnimatorSet;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Point;
import android.graphics.Rect;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;

import androidx.annotation.CallSuper;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.wwg.widget.IFolderOperate;
import com.wwg.widget.ISpringDraggable;
import com.wwg.widget.R;
import com.wwg.widget.ViewType;
import com.wwg.widget.fake.FakeBox;
import com.wwg.widget.fake.op.IFakeMaster;
import com.wwg.widget.fake.op.IFakeSlaver;
import com.wwg.widget.utils.DefaultAnimatorListener;

import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

// warning View.setBackground may cause request layout,
// when child view request layout, may call : parent.requestLayout
public class SpringBoardContentView extends ViewGroup implements ISpringDraggable, IFakeSlaver {
    public static final long FOLDER_ANIMATE_DURATION = 300L;

    private static final String TAG = "SpringBoardContentView";
    private static final int DEFAULT_HORIZONTAL_SPAN = 4;
    private static final int DEFAULT_MAX_LINES = -1;

    private final int mSpanCount;
    private final int maxLines;

    @Nullable
    @SuppressWarnings("rawtypes")
    private Adapter mAdapter;
    private final List<ViewHolder> mCachePool = new LinkedList<>();
    private final List<ViewHolder> mActivePool = new LinkedList<>();
    private final List<ViewHolder> mRecyclerPool = new LinkedList<>();

    private final int mVerticalGap;
    private int mHorizontalGap;
    private int mChildWidth;
    private int mChildHeight;

    private final int mTouchSlop;
    private final int mLongPressTimeout;

    // for touch event
    private boolean mCheckTargetTimeout = false;
    private final Runnable mPendingDetector = this::detectPending;
    private final Runnable mDraggableDetector = this::detectDraggable;
    private final Runnable mFolderHoverDetector = this::detectHoveredFolder;
    private final Runnable mRequestLayout = this::requestLayout;

    @Nullable
    private SpringBoardDragHelper mDragHelper;

    private boolean mShouldSpringBack = false;
    private boolean mClickedChild = false;
    private long mDownTime = 0L;
    private int mDownX;
    private int mDownY;
    private boolean mTouching = false;

    private final AnimatorHelper mAnimatorHelper;

    private IFolderOperate mFolderOPListener;

    private IFakeMaster mFakeMaster;
    private int mXOffset;
    private int mYOffset;

    private ItemClickListener mItemClickListener;

    public SpringBoardContentView(Context context) {
        this(context, null);
    }

    public SpringBoardContentView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public SpringBoardContentView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        this(context, attrs, defStyleAttr, R.style.SpringBoardContentViewDefaultStyle);
    }

    public SpringBoardContentView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
        mAnimatorHelper = new AnimatorHelper();

        Resources resources = getResources();
        TypedArray typedArray = context.obtainStyledAttributes(
                attrs,
                R.styleable.SpringBoardContentView,
                0,
                defStyleRes);
        mSpanCount = typedArray.getInteger(
                R.styleable.SpringBoardContentView_spanCount,
                DEFAULT_HORIZONTAL_SPAN
        );
        maxLines = typedArray.getInteger(
                R.styleable.SpringBoardContentView_android_maxLines,
                DEFAULT_MAX_LINES
        );
        mHorizontalGap = typedArray.getDimensionPixelSize(
                R.styleable.SpringBoardContentView_android_horizontalGap,
                -1
        );
        mVerticalGap = typedArray.getDimensionPixelSize(
                R.styleable.SpringBoardContentView_android_verticalGap,
                resources.getDimensionPixelSize(R.dimen.M8)
        );
        typedArray.recycle();

        mLongPressTimeout = ViewConfiguration.getLongPressTimeout();
        mTouchSlop = ViewConfiguration.getTouchSlop();
    }

    public void setItemClickListener(ItemClickListener listener) {
        this.mItemClickListener = listener;
    }

    @SuppressWarnings("rawtypes")
    public void setAdapter(@NonNull Adapter adapter) {
        // remove all listeners
        // removeAllViews();
        if (mAdapter != null) {
            for (ViewHolder holder : mActivePool) {
                mAdapter.onRecycleViewHolder(holder);
            }
            mRecyclerPool.addAll(mActivePool);

            mAdapter.injectView(null);
        }

        mActivePool.clear();
        adapter.injectView(this);
        mAdapter = adapter;
        requestLayout();
    }

    @Override
    public void requestLayout() {
        if (hasPendingAnimators()) {
            postRequest();
            return;
        }

        super.requestLayout();
    }

    /**
     * package private, only can used by {@link SpringBoardView}
     */
    void insertCurrentBySB() {
        if (mAdapter != null && mDragHelper != null) {
            int index = mAdapter.getItemCount() - 1;
            if (index >= 0) {
                Log.i(TAG, "insertCurrentBySpringBoard " + index);
                mDragHelper.updateCurrent(index);
            }
        }
    }

    void onFolderClosed() {
        if (mAdapter != null) {
            mAdapter.forceRefreshFolder(mActivePool);
        }
    }

    private void checkFolder() {
        if (mAdapter != null && mDragHelper != null) {
            int index = mAdapter.checkAndRemoveFolder();
            if (index == -1) {
                return;
            }

            ViewHolder holder = getHolder(index);
            if ((holder != null) && mAdapter.onItemRemoved(index)) {
                removeItemInternal(holder, true);
            }
        }
    }

    private void postRequest() {
        int REQUEST_LAYOUT_DELAY = 20;
        postDelayed(mRequestLayout, REQUEST_LAYOUT_DELAY);
    }

    private void clearRequest() {
        removeCallbacks(mRequestLayout);
    }

    /**
     * 测量过程会遍历得到子 view 最大的宽高，然后所有子 view 都将使用同样的尺寸。
     * 这样就会获得类似 recyclerView linearLayoutManager 的效果。
     * 但这样所有 item 都将被 measure 两次。
     *
     * @param widthMeasureSpec  width measureSpec
     * @param heightMeasureSpec height measureSpec
     */
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int measuredParentWidth = -1;
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int widthSize = MeasureSpec.getSize(widthMeasureSpec);

        LayoutParams plp = getLayoutParams();
        if ((widthMode == MeasureSpec.EXACTLY) || ((plp != null) && (plp.width == LayoutParams.MATCH_PARENT))) {
            measuredParentWidth = widthSize;
        }

        int measuredParentHeight = -1;
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if ((heightMode == MeasureSpec.EXACTLY) || ((plp != null) && (plp.height == LayoutParams.MATCH_PARENT))) {
            measuredParentHeight = heightSize;
        }

        int preLayoutParentWidth = getPaddingLeft() + getPaddingRight();
        int preLayoutParentHeight = getPaddingTop() + getPaddingBottom();

        int preLayoutChildWidth = 0;
        int preLayoutChildHeight = 0;

        int horizontalGap = mHorizontalGap;

        // preLayout children
        Adapter<ViewHolder> adapter = mAdapter;
        if (adapter != null) {
            int count = adapter.getItemCount();
            for (int index = 0; index < count; index++) {
                ViewHolder viewHolder = obtainChild(adapter, index);
                mCachePool.add(viewHolder);

                View childView = viewHolder.itemView;
                LayoutParams lp = childView.getLayoutParams();
                if (lp == null) {
                    lp = generateDefaultLayoutParams();
                    childView.setLayoutParams(lp);
                }

                int widthSpec;
                int childWidth = lp.width;
                if (childWidth <= 0) {
                    widthSpec = MeasureSpec.makeMeasureSpec(widthSize, MeasureSpec.UNSPECIFIED);
                } else {
                    widthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
                }

                int heightSpec;
                int childHeight = lp.height;
                if (childHeight <= 0) {
                    heightSpec = MeasureSpec.makeMeasureSpec(heightSize, MeasureSpec.UNSPECIFIED);
                } else {
                    heightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);
                }

                childView.measure(widthSpec, heightSpec);
                childView.forceLayout();

                int childMeasuredWidth = childView.getMeasuredWidth();
                int childMeasuredHeight = childView.getMeasuredHeight();

                if (preLayoutChildWidth < childMeasuredWidth) {
                    preLayoutChildWidth = childMeasuredWidth;
                }

                if (preLayoutChildHeight < childMeasuredHeight) {
                    preLayoutChildHeight = childMeasuredHeight;
                }
            }

            int span = getHorizontalSpan();
            float div = count * 1.f / span;
            int floor = (int) Math.floor(div);
            int ceil = (int) Math.ceil(div);

            preLayoutParentWidth += span * preLayoutChildWidth;
            int multiplier = (maxLines == DEFAULT_MAX_LINES) ? ceil : maxLines;
            preLayoutParentHeight += multiplier * preLayoutChildHeight;

            if (measuredParentWidth != -1 && span > 1) {
                horizontalGap = 0;

                int deltaWidth = measuredParentWidth - preLayoutParentWidth;
                if (deltaWidth > 0) {
                    horizontalGap = deltaWidth / (span - 1);
                }
            }

            if (span > 1) {
                preLayoutParentWidth += (span - 1) * horizontalGap;
            }

            if (floor > 1) {
                preLayoutParentHeight += (floor - 1) * mVerticalGap;
            }
        }

        if (measuredParentWidth == -1) {
            measuredParentWidth = preLayoutParentWidth;
            if (preLayoutParentWidth > widthSize) {
                Log.w(TAG, "It looks as if the content exceeds the width of the parent container, check your layout !");
            }
        }

        if (measuredParentHeight == -1) {
            measuredParentHeight = preLayoutParentHeight;
        }

        completeMeasure(preLayoutChildWidth, preLayoutChildHeight, horizontalGap);

        setMeasuredDimension(measuredParentWidth, measuredParentHeight);
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        // layout children
        Adapter<ViewHolder> adapter = mAdapter;
        if (adapter == null) {
            return;
        } else {
            removeAllViewsInLayout();
            for (ViewHolder holder : mActivePool) {
                adapter.onRecycleViewHolder(holder);
                mRecyclerPool.add(holder);
            }

            mActivePool.clear();
        }

        int currentIndex = -1;
        if (mDragHelper != null) {
            currentIndex = mDragHelper.getCurrentIndex();
        }

        Log.i(TAG, hashCode() + " on layout current index = " + currentIndex);

        int x = getPaddingStart();
        int y = getPaddingTop();

        int parentMeasureHeight = getMeasuredHeight();
        int parentMeasuredWidth = getMeasuredWidth();
        if (parentMeasuredWidth > 0 && parentMeasureHeight > 0) {
            int count = adapter.getItemCount();
            int childWidth = mChildWidth;
            int childHeight = mChildHeight;
            int horizontalGap = mHorizontalGap;
            if ((count > 0) && (childWidth <= 0 || childHeight <= 0 || horizontalGap < 0)) {
                Log.w(TAG, "should measure first");
                requestLayout();
                return;
            }

            clearRequest();
            for (int index = 0; index < count; index++) {
                //step 1: get viewHolder
                ViewHolder viewHolder = obtainChild(adapter, index);

                View childView = viewHolder.itemView;
                LayoutParams lp = childView.getLayoutParams();
                if (lp == null) {
                    lp = generateDefaultLayoutParams();
                }
                addViewInLayout(childView, -1, lp, true);
                mActivePool.add(viewHolder);

                //step 2: measure child
                int widthSpec = MeasureSpec.makeMeasureSpec(childWidth, MeasureSpec.EXACTLY);
                int heightSpec = MeasureSpec.makeMeasureSpec(childHeight, MeasureSpec.EXACTLY);

                childView.measure(widthSpec, heightSpec);

                //layout child

                int right = x + childWidth;
                int bottom = y + childHeight;
                childView.layout(x, y, right, bottom);

                x = horizontalGap + right;

                if (x >= (parentMeasuredWidth - getPaddingEnd())) {
                    x = getPaddingStart();
                    y = mVerticalGap + bottom;
                }

                if (index == currentIndex) {
                    childView.setVisibility(INVISIBLE);
                }
            }

            if (mShouldSpringBack) {
                mShouldSpringBack = false;
                doSpringBack();
            }
        }

        refreshParentInfo();
    }

    private void refreshParentInfo() {
        float x = getX();
        float y = getY();
        ViewParent parent = getParent();
        while (!(parent instanceof IFakeMaster)) {
            if (parent == null) {
                break;
            }

            x += ((View) parent).getX();
            y += ((View) parent).getY();

            parent = parent.getParent();
        }

        mFakeMaster = (IFakeMaster) parent;
        mXOffset = (int) x;
        mYOffset = (int) y;

        if (mDragHelper != null) {
            mDragHelper.injectMaster(mFakeMaster);
        }
    }

    ViewHolder obtainChild(@NonNull Adapter<ViewHolder> adapter, int index) {
        int type = adapter.getItemViewType(index);

        ViewHolder viewHolder = findInCachePool(index);
        if (viewHolder == null) {
            viewHolder = findInRecyclerPool(type);
            if (viewHolder == null) {
                viewHolder = adapter.createViewHolder(this, type);
            }

            adapter.bindViewHolder(viewHolder, index);
        }

        return viewHolder;
    }

    @Override
    public int getXOffsetRelativeToSpringBoard() {
        return mXOffset;
    }

    @Override
    public int getYOffsetRelativeToSpringBoard() {
        return mYOffset;
    }

    @Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();

        int horizontalSize = getPaddingStart() + getPaddingEnd();
        int verticalSize = getPaddingTop() + getPaddingBottom();
        if (!isLayoutRequested() && !isInLayout() &&
                (mAdapter != null) && (mAdapter.getItemCount() > 0) &&
                ((getMeasuredWidth() <= horizontalSize) || (getMeasuredHeight() <= verticalSize))) {
            requestLayout();
        }
    }

    @Override
    protected void onDetachedFromWindow() {
        mFakeMaster = null;
        mXOffset = 0;
        mYOffset = 0;

        super.onDetachedFromWindow();
    }

    void initDragHelper(int childWidth, int childHeight) {
        if (mDragHelper == null) {
            mDragHelper = new SpringBoardDragHelper(this);
        }

//        mDragHelper.injectMaster(mFakeMaster);
        mDragHelper.updateChildSize(childWidth, childHeight);
    }

    private @Nullable
    ViewHolder findInCachePool(int position) {
        ViewHolder target = null;

        int size = mCachePool.size();
        int index = -1;
        for (int pos = 0; pos < size; pos++) {
            target = mCachePool.get(pos);
            if (target.index == position) {
                index = pos;
                break;
            } else {
                target = null;
            }
        }

        if (index != -1) {
            mCachePool.remove(index);
        }

        return target;
    }

    private @Nullable
    ViewHolder findInRecyclerPool(@ViewType int type) {
        ViewHolder target = null;

        int size = mRecyclerPool.size();
        int index = -1;
        for (int pos = 0; pos < size; pos++) {
            target = mRecyclerPool.get(pos);
            if (target.type == type) {
                index = pos;
                break;
            } else {
                target = null;
            }
        }

        if (index != -1) {
            mRecyclerPool.remove(index);
        }

        return target;
    }

    void completeMeasure(int childWidth,
                         int childHeight,
                         int horizontalGap) {
//        if (childWidth > mChildWidth) {
        mChildWidth = childWidth;
//        }

//        if (childHeight > mChildHeight) {
        mChildHeight = childHeight;
//        }

        mHorizontalGap = horizontalGap;
        initDragHelper(mChildWidth, mChildHeight);
    }

    @Override
    public int getHorizontalGap() {
        return mHorizontalGap;
    }

    @Override
    public int getVerticalGap() {
        return mVerticalGap;
    }

    @Override
    public int getHorizontalSpan() {
        return mSpanCount;
    }

    @Override
    public int getDraggableCount() {
        return mActivePool.size();
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (mAdapter == null) {
            return super.onTouchEvent(event);
        }

        if (hasPendingAnimators()) {
            return true;
        }

        int action = event.getActionMasked();
        int x = (int) event.getRawX();
        int y = (int) event.getRawY();
        switch (action) {
            case MotionEvent.ACTION_DOWN:
                debugActivePool();
                // Log.i(TAG, "ACTION_DOWN");
                mClickedChild = true;
                mDownTime = System.currentTimeMillis();
                mCheckTargetTimeout = false;
                removeCallbacks(mPendingDetector);
                removeCallbacks(mDraggableDetector);
                ViewHolder target = findTarget(x, y);
                if ((mDragHelper != null) && (target != null)) {
                    mDragHelper.startCheckDrag(target);
                    postDelayed(mPendingDetector, mLongPressTimeout);
                    postDelayed(mDraggableDetector, 2L * mLongPressTimeout);
                }

                mDownX = x;
                mDownY = y;
                mTouching = true;
                return true;
            case MotionEvent.ACTION_MOVE:
                // Log.i(TAG, "ACTION_MOVE");
                int totalDeltaX = x - mDownX;
                int totalDeltaY = y - mDownY;
                if (Math.abs(totalDeltaX) > mTouchSlop || Math.abs(totalDeltaY) > mTouchSlop) {
                    mClickedChild = false;
                }

                if ((mDragHelper != null) && mDragHelper.hasPendingDragTarget() && !mCheckTargetTimeout) {
                    ViewHolder current = findTarget(x, y);
                    if (current == null) {
                        mDragHelper.interruptCheckDrag();
                        removeCallbacks(mPendingDetector);
                        removeCallbacks(mDraggableDetector);
                        // start scroll if need, or wait next touch event
                    } else {
                        if (mDragHelper.ensureDrag(current)) {
                            // must wait for check.
                            Log.i(TAG, "wait for check");
                        } else {
                            removeCallbacks(mPendingDetector);
                            removeCallbacks(mDraggableDetector);
                        }
                    }
                } else {
                    // todo scroll
                }
                break;
            case MotionEvent.ACTION_UP:
                // Log.i(TAG, "ACTION_UP");
                if (mClickedChild) {
                    long deltaT = System.currentTimeMillis() - mDownTime;
                    if ((deltaT <= mLongPressTimeout) && (mDragHelper != null)) {
                        ViewHolder pending = mDragHelper.getPendingCurrentTarget();
                        if ((pending != null) && (mItemClickListener != null)) {
                            mItemClickListener.onItemClick(pending);
                        }
                    }
                }
            case MotionEvent.ACTION_CANCEL:
                onTouchEventEnd(true);
                break;

            default:
                break;
        }

        return super.onTouchEvent(event);
    }

    @Override
    public void releaseDraggable() {
        if (mDragHelper == null || mAdapter == null) {
            return;
        }

        mAnimatorHelper.acceleratePendingAnimators();

        int currentIndex = mDragHelper.getCurrentIndex();
        if (currentIndex == -1) {
            return;
        }

        ViewHolder holder = getHolder(currentIndex);
        if ((holder != null) && mAdapter.onItemRemoved(currentIndex)) {
            removeItemInternal(holder, false);
            mDragHelper.resetCurrent();
        }
    }

    @Override
    public void onTouchEventEnd(boolean realEnd) {
        if (realEnd) {
            mTouching = false;
        }
        removeCallbacks(mPendingDetector);
        removeCallbacks(mDraggableDetector);
        removeCallbacks(mFolderHoverDetector);

        // final clean target
        if (mDragHelper != null) {
            mDragHelper.releasePendingDragTarget();

            int preDraped = mDragHelper.getDrapeRobe();
            if (preDraped >= 0 && preDraped < mActivePool.size()) {
                ViewHolder holder = getHolder(preDraped);
                if (holder != null && mAdapter != null) {
                    mAdapter.onItemHoverChanged(holder, false);
                }
            }
        }

        mCheckTargetTimeout = false;

        mDownX = mDownY = -1;
        mClickedChild = false;
    }

    @Override
    public void springBackFake(FakeBox fakeBox) {
        SpringBoardDragHelper dragHelper = mDragHelper;
        if (dragHelper == null) {
            return;
        }

        mAnimatorHelper.setPuppet(fakeBox);
        if (hasPendingAnimators() || isInLayout() || getWidth() <= 0 || getHeight() <= 0) {
            mShouldSpringBack = true;
            Log.i(TAG, "springBackFake set flag");
        } else {
            Log.i(TAG, "just do springBackFake");
            doSpringBack();
        }
    }

    private void doSpringBack() {
        SpringBoardDragHelper dragHelper = mDragHelper;
        if (dragHelper != null) {
            int currentIndex = dragHelper.getCurrentIndex();
            Log.i(TAG, "doSpringBack " + currentIndex);
            if (currentIndex != -1) {
                mAnimatorHelper.springBackCurrentTargetAnimated(currentIndex);
            } else {
                mAnimatorHelper.springBackAsInsertAnimated();
            }
        }
    }

    void detectPending() {
        if (mDragHelper != null) {
            mDragHelper.startPendingAnimate(mLongPressTimeout);
        }
    }

    void detectDraggable() {
        mCheckTargetTimeout = true;
        if (mAdapter == null) {
            return;
        }

        if (mDragHelper != null) {
            mDragHelper.fixedDragTarget(mAdapter);
            mDragHelper.clearDetectResult();
        }
    }

    @Override
    public void onFakeDragged(int x, int y) {
        SpringBoardDragHelper dragHelper = mDragHelper;
        if (dragHelper == null) {
            return;
        }

        Adapter<ViewHolder> adapter = mAdapter;
        if (adapter == null) {
            return;
        }

        int preDrapeRobe = dragHelper.getDrapeRobe();
        int preCrowdOut = dragHelper.getCrowdOut();
        if (preCrowdOut != -1) {
            return;
        }

        int[] detectResult = dragHelper.detectRegion(x, y);
//        Log.i(TAG, "detectRegion：" + detectResult[0] + " " + detectResult[1]);
        int drapedRobe = detectResult[0];
        if (drapedRobe != -1) {
            boolean couldNotMakeFolder = !adapter.canMakeFolder(drapedRobe, mTouching);
            if (mFolderOPListener == null || couldNotMakeFolder) {
                if (couldNotMakeFolder) {
                    mTouching = false;
                }
                drapedRobe = -1;
            }
        }

        int crowdOut = detectResult[1];
        if (crowdOut != -1 || ((preDrapeRobe != -1) && (drapedRobe != preDrapeRobe))) {
            removeCallbacks(mFolderHoverDetector);
            ViewHolder preDraped = getHolder(preDrapeRobe);
            if (preDraped != null) {
                adapter.onItemHoverChanged(preDraped, false);
            }
        }

        if (crowdOut != -1) {
            Log.i(TAG, "应该挤走：" + crowdOut);
            mAnimatorHelper.runAutoAlignAnimator(crowdOut);
        } else if ((drapedRobe != dragHelper.getCurrentIndex()) && (drapedRobe != -1)) {
            ViewHolder draped = getHolder(drapedRobe);
            if (draped != null) {
                adapter.onItemHoverChanged(draped, true);
                postDelayed(mFolderHoverDetector, mLongPressTimeout);
            }
        }

        dragHelper.feedbackDetectResult(detectResult);
    }

    @Nullable
    private ViewHolder getHolder(int index) {
        if (index >= 0 && index < mActivePool.size()) {
            ViewHolder result = mActivePool.get(index);
            if ((result != null) && (result.index == index)) {
                return result;
            } else {
                for (ViewHolder holder : mActivePool) {
                    if (holder.index == index) {
                        return holder;
                    }
                }
            }
        }

        return null;
    }

    @Nullable
    ViewHolder findTarget(int x, int y) {
        int[] childLoc = new int[2];
        for (ViewHolder holder : mActivePool) {
            View child = holder.itemView;
            int width = child.getWidth();
            int height = child.getHeight();
            if (width <= 0 || height <= 0) {
                continue;
            }

            child.getLocationOnScreen(childLoc);
            int left = childLoc[0];
            int right = left + width;
            if (x < left || x > right) {
                continue;
            }

            int top = childLoc[1];
            int bottom = top + height;
            if (y < top || y > bottom) {
                continue;
            }

            return holder;
        }

        return null;
    }

    int getActiveSize() {
        return mActivePool.size();
    }

    boolean removeItemInternal(ViewHolder holder, boolean animated) {
        return mAnimatorHelper.removeItemAnimated(holder, animated);
    }

    boolean addItemInternal() {
        Adapter<ViewHolder> adapter = mAdapter;
        if (mAdapter == null) {
            return false;
        }

        int count = adapter.getItemCount();
        if (count <= 0) {
            return false;
        } else if ((count == 1) || (mChildWidth <= 0) || (mChildHeight <= 0) || (mHorizontalGap < 0)) {
            requestLayout();
            return true;
        }
        /*
        //step 1: get viewHolder
        int position = count - 1;
        ViewHolder viewHolder = obtainChild(adapter, position);
        View childView = viewHolder.itemView;
        LayoutParams lp = childView.getLayoutParams();
        if (lp == null) {
            lp = generateDefaultLayoutParams();
        }
        addViewInLayout(childView, -1, lp, true);

        //step 2: measure child
        int widthSpec = MeasureSpec.makeMeasureSpec(mChildWidth, MeasureSpec.EXACTLY);
        int heightSpec  = MeasureSpec.makeMeasureSpec(mChildHeight, MeasureSpec.EXACTLY);
        childView.measure(widthSpec, heightSpec);
        int measuredWidth = childView.getMeasuredWidth();
        int measuredHeight = childView.getMeasuredHeight();

        //step 3: layout child
        int x = getPaddingStart();
        int y = getPaddingTop();
        int lastActiveIndex = mActivePool.size() - 1;
        if (lastActiveIndex >= 0) {
            ViewHolder lastActive = mActivePool.get(lastActiveIndex);
            if (lastActive != null) {
                int span = getHorizontalSpan();
                int column = position % span;

                y = (int) lastActive.itemView.getY();
                if (column > 0) {
                    x = (int) (lastActive.itemView.getX() + mChildWidth + mHorizontalGap);
                }

                if (column == 0) {
                    y += (mVerticalGap + mChildHeight);
                }
            }
        }

        childView.layout(x, x + mChildWidth, y, y + mChildHeight);
        mActivePool.add(viewHolder);
        childView.postInvalidateDelayed(100);
//        invalidate(childView.getLeft(), childView.getTop(), childView.getRight(), childView.getBottom());

        //how to call draw?

        */
        return true;
    }

    private boolean hasPendingAnimators() {
        if (mAnimatorHelper == null) {
            return false;
        }

        return mAnimatorHelper.hasPendingAnimators();
    }

    @Override
    protected LayoutParams generateLayoutParams(LayoutParams p) {
        return new LayoutParams(p);
    }

    @Override
    public LayoutParams generateLayoutParams(AttributeSet attrs) {
        return new LayoutParams(getContext(), attrs);
    }

    @Override
    protected LayoutParams generateDefaultLayoutParams() {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    }

    public void setFolderOPListener(IFolderOperate listener) {
        mFolderOPListener = listener;
    }

    void openFolder(int position) {
        SpringBoardDragHelper dragHelper = mDragHelper;
        if (dragHelper == null) {
            return;
        }

        if (mFolderOPListener != null) {
            int x = 0;
            int y = 0;
            Rect rect = dragHelper.getRectCorrectWithSpringBoard(position);
            if (rect != null) {
                x = rect.left;
                y = rect.top;
            }

            mFolderOPListener.openFolder(new Rect(x, y, x + mChildWidth, y + mChildHeight));
        }
    }

    void detectHoveredFolder() {
        SpringBoardDragHelper dragHelper = mDragHelper;
        if (dragHelper == null) {
            return;
        }

        int index = dragHelper.getDrapeRobe();
        if (index != -1) {
            ViewHolder holder = getHolder(index);
            if (holder != null) {
                if (holder.type == ViewType.NORMAL) {
                    dragHelper.confirmedDrapeRobe();
                    return;
                }
            }

            openFolder(index);
        }
    }

    private class AnimatorHelper {
        private static final int MOVE_DURATION = 150;

        private AnimatorSet mForceInterruptFlag = null;
        private boolean mAccelerateFlag = false;

        @Nullable
        private AnimatorSet mRunningAnimatorSet;
        private final Queue<AnimatorSet> mPendingAnimators = new LinkedList<>();

        private final DefaultAnimatorListener mEndAnimatorListener = new DefaultAnimatorListener() {
            @Override
            public void onAnimationEnd(Animator animation) {
                runPendingAnimators(false);
            }
        };

        @Nullable
        private FakeBox mPuppet;

        public void setPuppet(@NonNull FakeBox puppet) {
            mPuppet = puppet;
        }

        void springBackAsInsertAnimated() {
            final SpringBoardDragHelper dragHelper = SpringBoardContentView.this.mDragHelper;
            if (dragHelper == null) {
                return;
            }

            if (mAdapter == null) {
                return;
            }

            if (mPuppet == null) {
                return;
            }

            if (mAdapter.onItemInserted(mPuppet.data)) {
                Rect rect = dragHelper.getLogicRectCorrectWithSpringBoard(mAdapter.getItemCount() - 1);
                if (rect == null) {
                    return;
                }

                View puppet = mPuppet.view;
                mPuppet = null;

                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.setDuration(MOVE_DURATION);
                ValueAnimator animator = springBackAnimated(rect, puppet);

                animatorSet.playTogether(animator);
                animatorSet.addListener(new DefaultAnimatorListener() {
                    @Override
                    public void onAnimationEnd(Animator animation) {
                        if (mDragHelper != null) {
                            mDragHelper.resetCurrent();
                        }

//                        postDelayed(SpringBoardContentView.this::addItemInternal, 20);
                        requestLayout();
                        postDelayed(() -> {
                            finallyRemovePuppet(puppet);
                            checkFolder();
                        }, 50);
                    }
                });

                enqueueAnimators(animatorSet);
            }
        }

        void springBackCurrentTargetAnimated(int index) {
            final SpringBoardDragHelper dragHelper = SpringBoardContentView.this.mDragHelper;
            if (dragHelper == null) {
                return;
            }

            if (mPuppet == null) {
                return;
            }

            ViewHolder dragging = getHolder(index);
            int confirmedDrapeRobe = dragHelper.getConfirmedDrapeRobe();
            Log.d(TAG, "springBackCurrentTargetAnimated " + confirmedDrapeRobe);
            if (confirmedDrapeRobe != -1 && dragging != null) {
                ViewHolder confirm = getHolder(confirmedDrapeRobe);
                if (confirm != null) {
                    runNewFolderAnimator(confirm, dragging);
                    return;
                }
            }

            Rect targetCorrectWithSpringBoard = dragHelper.getLogicRectCorrectWithSpringBoard(index);
            if (targetCorrectWithSpringBoard == null) {
                Log.w(TAG, "springBackCurrentTargetAnimated rect == null");
                return;
            }

            View puppet = mPuppet.view;
            mPuppet = null;

            final View targetView = (dragging == null) ? null : dragging.itemView;

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.setDuration(MOVE_DURATION);
            ValueAnimator animator = springBackAnimated(targetCorrectWithSpringBoard, puppet);
            animator.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (targetView != null) {
                        targetView.setVisibility(VISIBLE);
                        targetView.setScaleX(1f);
                        targetView.setScaleY(1f);
                    }

                    finallyRemovePuppet(puppet);
                }
            });

            animatorSet.playTogether(animator);
            animatorSet.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (mDragHelper != null) {
                        mDragHelper.resetCurrent();
                    }
                    requestLayout();
                    postDelayed(SpringBoardContentView.this::checkFolder, 50);
                }
            });

            enqueueAnimators(animatorSet);
        }

        ValueAnimator springBackAnimated(Rect rect, View puppet) {
            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);

            // int deltaX = rect.left - targetLoc[0];
            // int deltaY = rect.top - targetLoc[1];
            int deltaX = (int) (rect.left - puppet.getX());
            int deltaY = (int) (rect.top - puppet.getY());
            float targetTX = puppet.getTranslationX();
            float targetTY = puppet.getTranslationY();

            float initScaleX = puppet.getScaleX();
            float initScaleY = puppet.getScaleY();

            animator.addUpdateListener(animation -> {
                float fraction = (float) animation.getAnimatedValue();
                puppet.setTranslationX(fraction * deltaX + targetTX);
                puppet.setTranslationY(fraction * deltaY + targetTY);

                puppet.setScaleX(initScaleX + (1 - initScaleX) * fraction);
                puppet.setScaleY(initScaleY + (1 - initScaleY) * fraction);
            });
            animator.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    puppet.setTranslationX(deltaX + targetTX);
                    puppet.setTranslationY(deltaY + targetTY);

                    puppet.setScaleX(1);
                    puppet.setScaleY(1);
                }
            });

            return animator;
        }

        void runNewFolderAnimator(ViewHolder confirmed, ViewHolder target) {
            if (mPuppet == null) {
                return;
            }

            if (mAdapter != null) {
                mAdapter.onMakeFolder(confirmed.index, target.index);
            }

            View puppet = mPuppet.view;
            mPuppet = null;

            View confirmedView = confirmed.itemView;
            int x = (int) confirmedView.getX();
            int y = (int) confirmedView.getY();
            Rect rect = new Rect(x, y, x + confirmedView.getWidth(), y + confirmedView.getHeight());
            ValueAnimator animator = springBackAnimated(rect, puppet);

            animator.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    finallyRemovePuppet(puppet);
                }
            });

            AnimatorSet animatorSet = generateRemoveItemAnimator(target, true);
            animatorSet.playTogether(animator);
            animatorSet.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    if (mDragHelper != null) {
                        mDragHelper.resetCurrent();
                    }
                }
            });
            enqueueAnimators(animatorSet);
        }

        private void finallyRemovePuppet(View puppet) {
            ViewParent parent = puppet.getParent();
            if (parent != null) {
                ((ViewGroup) parent).removeView(puppet);
            }

            AnimatorHelper.this.mPuppet = null;
        }

        void runAutoAlignAnimator(int startPos) {
            final SpringBoardDragHelper dragHelper = SpringBoardContentView.this.mDragHelper;
            if (dragHelper == null) {
                return;
            }

            if (mAdapter == null) {
                return;
            }

            int endPos = dragHelper.getCurrentIndex();
            final ViewHolder startHolder = getHolder(startPos);
            final ViewHolder endHolder = getHolder(endPos);
            if (startHolder == null || endHolder == null) {
                return;
            }

            AnimatorSet animatorSet = new AnimatorSet();
            animatorSet.setDuration(MOVE_DURATION);
            List<Animator> animators = new LinkedList<>();

            if (startPos > endPos) {
                for (int i = startPos; i > endPos; i--) {
                    Log.i(TAG, "runAutoAlignAnimator " + i);
                    Animator animator = generateAutoAlignAnimator(i, i - 1);
                    if (animator != null) {
                        animators.add(animator);
                    }
                }

            } else {
                for (int i = startPos; i < endPos; i++) {
                    Log.i(TAG, "runAutoAlignAnimator " + i);
                    Animator animator = generateAutoAlignAnimator(i, i + 1);
                    if (animator != null) {
                        animators.add(animator);
                    }
                }

            }

            mAdapter.onOrderChanged(startPos, endPos);

            int deltaX = (int) (startHolder.itemView.getX() - endHolder.itemView.getX());
            int deltaY = (int) (startHolder.itemView.getY() - endHolder.itemView.getY());
            float fromTX = endHolder.itemView.getTranslationX();
            float fromTY = endHolder.itemView.getTranslationY();
            endHolder.itemView.setTranslationX(deltaX + fromTX);
            endHolder.itemView.setTranslationY(deltaY + fromTY);

            animatorSet.playTogether(animators);
            animatorSet.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    endHolder.index = startPos;

                    Collections.sort(mActivePool, (o1, o2) -> Integer.compare(o1.index, o2.index));
                    dragHelper.updateCurrent(startPos);

                    if (mShouldSpringBack) {
                        mShouldSpringBack = false;
                        doSpringBack();
                    }

                    dragHelper.clearDetectResult();
                }
            });
            enqueueAnimators(animatorSet);
        }

        @Nullable
        private Animator generateAutoAlignAnimator(int fromIdx, int toIdx) {
            SpringBoardDragHelper dragHelper = SpringBoardContentView.this.mDragHelper;
            if (dragHelper == null) {
                return null;
            }

            ViewHolder from = getHolder(fromIdx);
            if (from == null) {
                return null;
            }

            ViewHolder to = getHolder(toIdx);
            if (to == null) {
                return null;
            }

            float fromX = from.itemView.getX();
            float fromY = from.itemView.getY();
            float toX = to.itemView.getX();
            float toY = to.itemView.getY();

            int deltaX = (int) (toX - fromX);
            int deltaY = (int) (toY - fromY);
            float fromTX = from.itemView.getTranslationX();
            float fromTY = from.itemView.getTranslationY();

            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    from.index = toIdx;
                    from.itemView.setTranslationX(deltaX + fromTX);
                    from.itemView.setTranslationY(deltaY + fromTY);
                }
            });
            animator.addUpdateListener(animation -> {
                float fraction = (float) animation.getAnimatedValue();
                from.itemView.setTranslationX(fraction * deltaX + fromTX);
                from.itemView.setTranslationY(fraction * deltaY + fromTY);
            });
            return animator;
        }

        AnimatorSet generateRemoveItemAnimator(ViewHolder holder, boolean animated) {
            int size = mActivePool.size();
            int index = holder.index;
            List<Animator> animators = new LinkedList<>();
            AnimatorSet animatorSet = new AnimatorSet();
            holder.itemView.setAlpha(0f);
            ViewHolder to = holder;
            ViewHolder from = null;
            for (int i = index + 1; i < size; i++) {
                if (from != null) {
                    to = from;
                }

                from = mActivePool.get(i);
                Animator animator = generateChildViewMoveAnimator(from, to);
                if (animator != null) {
                    animators.add(animator);
                }
            }

            animatorSet.setDuration(animated ? MOVE_DURATION : 50);
            animatorSet.playTogether(animators);
            animatorSet.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    // do not requestLayout immediately, for check pending animator return false
                    // removeViewInLayout(holder.itemView);
                    // mActivePool.remove(holder);
                    // if (mAdapter != null) {
                    //     mAdapter.onRecycleViewHolder(holder);
                    // }
                    // mRecyclerPool.add(holder);

                    requestLayout();
                }
            });

            return animatorSet;
        }

        boolean removeItemAnimated(ViewHolder holder, boolean animated) {
            enqueueAnimators(generateRemoveItemAnimator(holder, animated));
            return true;
        }

        @Nullable
        private Animator generateChildViewMoveAnimator(
                @NonNull ViewHolder from, @NonNull ViewHolder to) {
            if (mDragHelper == null) {
                return null;
            }

            Rect fromRect = mDragHelper.getRectCorrectWithParent(from.index);
            if (fromRect == null) {
                return null;
            }

            Rect toRect = mDragHelper.getRectCorrectWithParent(to.index);
            if (toRect == null) {
                return null;
            }

            int deltaX = toRect.left - fromRect.left;
            int deltaY = toRect.top - fromRect.top;
            float fromTX = from.itemView.getTranslationX();
            float fromTY = from.itemView.getTranslationY();
            int newIndex = to.index;

            ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
            animator.addListener(new DefaultAnimatorListener() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    from.index = newIndex;
                    from.itemView.setTranslationX(deltaX + fromTX);
                    from.itemView.setTranslationY(deltaY + fromTY);
                }
            });
            animator.addUpdateListener(animation -> {
                float fraction = (float) animation.getAnimatedValue();
                from.itemView.setTranslationX(fraction * deltaX + fromTX);
                from.itemView.setTranslationY(fraction * deltaY + fromTY);
            });
            return animator;
        }

        boolean hasPendingAnimators() {
            if (mRunningAnimatorSet != null) {
                if ((mForceInterruptFlag == mRunningAnimatorSet) &&
                        !mRunningAnimatorSet.isStarted()) {
                    mForceInterruptFlag = mRunningAnimatorSet = null;
                } else if (!mRunningAnimatorSet.isStarted()) {
                    mForceInterruptFlag = mRunningAnimatorSet;
                } else {
                    mForceInterruptFlag = null;
                }

                return true;
            }

            mForceInterruptFlag = null;
            return !mPendingAnimators.isEmpty();
        }

        private void enqueueAnimators(AnimatorSet set) {
            mPendingAnimators.add(set);
            runPendingAnimators(true);
        }

        private void runPendingAnimators(boolean checkRunning) {
            AnimatorSet animatorSet = this.mRunningAnimatorSet;
            if (animatorSet != null) {
                if (mAccelerateFlag) {
                    mRunningAnimatorSet = null;
                    animatorSet.end();
                } else if (checkRunning && animatorSet.isRunning()) {
                    return;
                }

                animatorSet.removeAllListeners();
                mRunningAnimatorSet = null;
            }

            AnimatorSet next = mPendingAnimators.poll();
            if (next != null) {
                next.addListener(mEndAnimatorListener);
                mRunningAnimatorSet = next;
                if (mAccelerateFlag) {
                    next.end();
                } else {
                    next.start();
                }
            } else {
                mAccelerateFlag = false;
                mRunningAnimatorSet = null;
                if (mShouldSpringBack) {
                    mShouldSpringBack = false;
                    doSpringBack();
                }
            }
        }

        void acceleratePendingAnimators() {
            mAccelerateFlag = true;
            runPendingAnimators(true);
        }
    }

    public interface ItemClickListener {
        void onItemClick(ViewHolder holder);
    }

    public static abstract class Adapter<VH extends ViewHolder> implements ItemClickListener {
        @Nullable
        private SpringBoardContentView mView = null;

        void injectView(SpringBoardContentView view) {
            mView = view;
            if (mView != null) {
                mView.setItemClickListener(this);
            }
        }

        @Override
        public void onItemClick(ViewHolder holder) {
            if (holder.type == ViewType.FOLDER) {
                if (mView != null) {
                    mView.openFolder(holder.index);
                }
            }
        }

        public abstract int getItemCount();

        public abstract Object getItem(int index);

        @ViewType
        public abstract int getItemViewType(int position);

        @NonNull
        final VH createViewHolder(@NonNull ViewGroup parent, @ViewType int viewType) {
            final VH vh = onCreateViewHolder(parent, viewType);
            vh.type = viewType;
            return vh;
        }

        final void bindViewHolder(@NonNull VH holder, int position) {
            holder.index = position;
            onBindViewHolder(holder, position);
        }

        @CallSuper
        protected void onRecycleViewHolder(ViewHolder holder) {
            holder.itemView.setVisibility(VISIBLE);
            holder.itemView.setAlpha(1f);
            holder.itemView.setScaleX(1f);
            holder.itemView.setScaleY(1f);
            holder.itemView.setTranslationX(0f);
            holder.itemView.setTranslationY(0f);
            holder.index = -1;
        }

        @NonNull
        protected abstract VH onCreateViewHolder(@NonNull ViewGroup parent, @ViewType int viewType);

        protected abstract void onBindViewHolder(@NonNull VH holder, int position);

        /**
         * adapter passive acceptance
         *
         * @param position position will be removed
         */
        protected abstract boolean onItemRemoved(int position);

        protected abstract boolean onOrderChanged(int one, int another);

        /**
         * adapter active trigger
         *
         * @param holder the holder will be removed
         * @return true successfully, false otherwise
         */
        protected boolean notifyItemRemoved(ViewHolder holder) {
            if (mView == null) {
                Log.e(TAG, "notifyItemRemoved error, view not attach!");
                return true;
            }

//            if (mView.hasPendingAnimators()) {
//                return false;
//            }

            if (checkInValid()) {
                return false;
            }

            int index = holder.index;
            int size = getItemCount();
            if (index == -1 || index >= size) {
                return false;
            }

            return mView.removeItemInternal(holder, true);
        }

        protected abstract void onItemHoverChanged(ViewHolder holder, boolean hovered);

        protected boolean canMakeFolder(int position, boolean shouldWarning) {
            return false;
        }

        protected boolean canMoveToOutside(boolean shouldWarning) {
            return true;
        }

        protected void onMakeFolder(int origin, int sojourner) {
        }

        protected void forceRefreshFolder(List<ViewHolder> actives) {
        }

        /**
         * adapter passive acceptance
         */
        protected abstract boolean onItemInserted(Object insert);

        protected int checkAndRemoveFolder() {
            return -1;
        }

        protected boolean notifyItemInsert() {
            if (mView == null) {
                Log.e(TAG, "notifyItemRemoved error, view not attach!");
                return false;
            }

            if (mView.hasPendingAnimators()) {
                return false;
            }

            int activeSize = mView.getActiveSize();
            int itemCount = getItemCount();
            if (itemCount == activeSize + 1) {
                return mView.addItemInternal();
            }

            return false;
        }

        private boolean checkInValid() {
            if (mView == null) {
                Log.e(TAG, "notifyItemRemoved error, view not attach!");
                return true;
            }

            int activeSize = mView.getActiveSize();
            int itemCount = getItemCount();
            if (activeSize != itemCount) {
                Log.e(TAG, "active size" + activeSize + " not equal adapter size" + itemCount + ", break!");
                throw new RuntimeException("can not process size mismatch");
                //TODO should force match size ?
//                mView.requestLayout();
            }

            return false;
        }

        public void notifyDataSetChanged() {
            if (mView != null) {
                mView.requestLayout();
            }
        }
    }

    public abstract static class ViewHolder {
        @NonNull
        public final View itemView;

        @ViewType
        public int type;

        public int index = -1;

        public ViewHolder(@NonNull View view) {
            this.itemView = view;
        }
    }

    private void debugActivePool() {
        int index = 0;
        for (ViewHolder holder : mActivePool) {
            Log.i(TAG, "holder index " + (index++) + " : " + holder);
        }
    }
}
